
#include "nane/SoundSystem/OAL/OALSoundSystem.h"
#include "nane/SoundSystem/OAL/OALSoundBuffer.h"
#include "nane/SoundSystem/OAL/OALSoundSource.h"

#include "nane/core/log.h"

namespace nane
{
    ////////////////////////////////////////////////////////////////////////////////////////////////
#if defined(WIN32)
    const char s_oalModuleName[] = "OpenAL32.dll";
#else
    const char s_oalModuleName[] = "libopenal.so.1";
#endif
    ////////////////////////////////////////////////////////////////////////////////////////////////
    static const uint32 MAX_SOUND_SOURCES = 32;
    ////////////////////////////////////////////////////////////////////////////////////////////////
    OALSoundSystem::OALSoundSystem()
        : m_device( NULL )
        , m_context( NULL )
    {

    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    OALSoundSystem::~OALSoundSystem()
    {

    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool OALSoundSystem::Initialize()
    {
        if( InitializeOpenALAPI(s_oalModuleName) == false )
        {
            LOG_ERROR("Failed to load OpenAL API");
            return false;
        }
        m_device = OpenALAPI::alcOpenDevice(NULL); // open default device
        if( m_device == NULL )
        {
            LOG_ERROR("Failed to open default OpenAL device");
            return false;
        }
        return true;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OALSoundSystem::Shutdown()
    {
        if( OpenALAPI::valid == true )
        {
            if( m_device != NULL )
            {
                OpenALAPI::alcCloseDevice(m_device);
                m_device = NULL;
            }
        }
        ReleaseOpenALAPI();
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool OALSoundSystem::InitializeContext( float _speedOfSound )
    {
        m_context = OpenALAPI::alcCreateContext(m_device, NULL);
        if( m_context == NULL )
        {
            LOG_ERROR("Failed to create OpenAL context");
            return false;
        }
        OpenALAPI::alcMakeContextCurrent(m_context);
        
        for( uint32 i = 0; i < MAX_SOUND_SOURCES; ++i )
        {
            ALuint alSource = 0;
            OpenALAPI::alGenSources(1, &alSource);
            if( OpenALAPI::alGetError() != AL_NO_ERROR )
            {
                break;
            }
            SetFreeSource_(alSource);
        }
        return true;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OALSoundSystem::ShutdownContext()
    {
        OpenALAPI::alcDestroyContext(m_context);
        m_context = NULL;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    ISoundBuffer* OALSoundSystem::CreateSoundBuffer( ESoundFormat::raw_type _format, uint32 _frequency, uint16 _channels
                                            , uint16 _bitsPerSample, uint32 _size )
    {
        ESoundFormat format = enum_cast<ESoundFormat>(_format);
        if( format != ESoundFormat::PCM
            || _channels > 2 )                  // only mono/stereo PCM supported for now
        {
            return NULL;
        }

        ALuint alBufferID = 0;
        OpenALAPI::alGenBuffers(1, &alBufferID);
        if( OpenALAPI::alGetError() != AL_NO_ERROR )
        {
            return NULL;
        }
        byte* buffer = new byte[_size];
        ALenum alFormat = _channels == 1 ? (_bitsPerSample == 8 ? AL_FORMAT_MONO8 : AL_FORMAT_MONO16) : (_bitsPerSample == 8 ? AL_FORMAT_STEREO8 : AL_FORMAT_STEREO16);
        OpenALAPI::alBufferData(alBufferID, alFormat, buffer, _size, _frequency);
        if( OpenALAPI::alGetError() != AL_NO_ERROR )
        {
            delete[] buffer;
            OpenALAPI::alDeleteBuffers(1, &alBufferID);
            return NULL;
        }
        OALSoundBuffer* soundBuffer = new OALSoundBuffer(alBufferID, alFormat, _frequency, _channels, _bitsPerSample, buffer, _size);
        return soundBuffer;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    byte* OALSoundSystem::LockSoundBuffer( ISoundBuffer* _buffer )
    {
        OALSoundBuffer* oalSoundBuffer = static_cast<OALSoundBuffer*>(_buffer);
        return oalSoundBuffer->buffer;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OALSoundSystem::UnlockSoundBuffer( ISoundBuffer* _buffer )
    {
        OALSoundBuffer* oalSoundBuffer = static_cast<OALSoundBuffer*>(_buffer);
        OpenALAPI::alBufferData(oalSoundBuffer->bufferID, oalSoundBuffer->format, oalSoundBuffer->buffer
                                , oalSoundBuffer->size, oalSoundBuffer->frequency);
        if( ALenum error = OpenALAPI::alGetError() )
        {
            LOG_ERROR("Failed to write OAL buffer data (%1)") << OpenALAPI::alGetString(error);
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OALSoundSystem::ReleaseSoundBuffer( ISoundBuffer* _buffer )
    {
        OALSoundBuffer* oalSoundBuffer = static_cast<OALSoundBuffer*>(_buffer);
        delete[] oalSoundBuffer->buffer;
        OpenALAPI::alDeleteBuffers(1, &oalSoundBuffer->bufferID);
        if( ALenum error = OpenALAPI::alGetError() )
        {
            LOG_ERROR("Error while releasing OAL Sound buffer (%1)") << OpenALAPI::alGetString(error);
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OALSoundSystem::SetListenerPosition(float _x, float _y, float _z)
    {
        OpenALAPI::alListener3f(AL_POSITION, _x, _y, _z);
        if( ALenum error = OpenALAPI::alGetError() )
        {
            LOG_ERROR("Failed to set listener position") << OpenALAPI::alGetString(error);
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OALSoundSystem::SetListenerOrientation( float _atX, float _atY, float _atZ, float _upX, float _upY, float _upZ )
    {
        float orientation[] = { _atX, _atY, _atZ, _upX, _upY, _upZ };
        OpenALAPI::alListenerfv(AL_ORIENTATION, orientation);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OALSoundSystem::SetListenerVelocity( float _x, float _y, float _z )
    {
        OpenALAPI::alListener3f(AL_VELOCITY, _x, _y, _z);
        if( ALenum error = OpenALAPI::alGetError() )
        {
            LOG_ERROR("Failed to set listener velocity") << OpenALAPI::alGetString(error);
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    ISoundSource* OALSoundSystem::CreateSoundSource()
    {
        OALSoundSource* oalSoundSource = new OALSoundSource();
        return oalSoundSource;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    ISoundSource3D* OALSoundSystem::CreateSoundSource3D()
    {
        OALSoundSource3D* oalSoundSource = new OALSoundSource3D();
        return oalSoundSource;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OALSoundSystem::SetSource3DPosition( ISoundSource3D* _source, float _x, float _y, float _z )
    {
        OALSoundSource3D* oalSoundSource3D = static_cast<OALSoundSource3D*>(_source);
        oalSoundSource3D->x = _x;
        oalSoundSource3D->y = _y;
        oalSoundSource3D->z = _z;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OALSoundSystem::SetSource3DVelocity( ISoundSource3D* _source, float _x, float _y, float _z )
    {
        OALSoundSource3D* oalSoundSource3D = static_cast<OALSoundSource3D*>(_source);
        oalSoundSource3D->vx = _x;
        oalSoundSource3D->vy = _y;
        oalSoundSource3D->vz = _z;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool OALSoundSystem::QueueSourceBuffer( ISoundSource* _source, ISoundBuffer* _buffer )
    {
        OALSoundSource* oalSoundSource = static_cast<OALSoundSource*>(_source);
        oalSoundSource->buffers.push_back(_buffer);
        return true;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    uint32 OALSoundSystem::GetSourceBuffersQueued( ISoundSource* _source )
    {
        OALSoundSource* oalSoundSource = static_cast<OALSoundSource*>(_source);
        return oalSoundSource->buffers.size();
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OALSoundSystem::ClearSourceBufferQueue( ISoundSource* _source )
    {
        OALSoundSource* oalSoundSource = static_cast<OALSoundSource*>(_source);
        oalSoundSource->buffers.clear();
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OALSoundSystem::Play( ISoundSource* _source, uint32 _posMs )
    {
        OALSoundSource* oalSoundSource = static_cast<OALSoundSource*>(_source);
        if( oalSoundSource->buffers.empty() )
        {
            return;
        }
        
        oalSoundSource->source = GetFreeSource_();
        if( oalSoundSource->source == 0 )       // no free sources to play on
        {
            return;
        }

        oalSoundSource->Play(_posMs);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OALSoundSystem::Stop( ISoundSource* _source )
    {
        OALSoundSource* oalSoundSource = static_cast<OALSoundSource*>(_source);
        OpenALAPI::alSourceStop(oalSoundSource->source);
        if( ALenum error = OpenALAPI::alGetError() )
        {
            LOG_ERROR("Error while stopping OAL source (%1)") << OpenALAPI::alGetString(error);
        }
        SetFreeSource_(oalSoundSource->source);
        oalSoundSource->source = 0;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OALSoundSystem::ReleaseSoundSource( ISoundSource* _source )
    {
        OALSoundSource* oalSoundSource = static_cast<OALSoundSource*>(_source);
        delete oalSoundSource;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    ALuint OALSoundSystem::GetFreeSource_()
    {
        if( m_freeSources.empty() )
        {
            return 0;
        }
        ALuint source = m_freeSources.back();
        m_freeSources.pop_back();
        return source;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OALSoundSystem::SetFreeSource_( ALuint _source )
    {
        OpenALAPI::alSourcei(_source, AL_BUFFER, AL_NONE);
        if( ALenum error = OpenALAPI::alGetError() )
        {
            LOG_ERROR("Error while resetting OAL source (%1)") << OpenALAPI::alGetString(error);
        }

        OpenALAPI::alSourceRewind(_source);
        if( ALenum error = OpenALAPI::alGetError() )
        {
            LOG_ERROR("Error while resetting OAL source (%1)") << OpenALAPI::alGetString(error);
        }
        m_freeSources.push_back(_source);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
}   // namespace nane
